package com.essence.common.model;

import com.essence.common.model.parser.InfoObjectParser;
import io.netty.buffer.ByteBuf;
import io.netty.buffer.Unpooled;
import org.slf4j.Logger;
import org.slf4j.LoggerFactory;

import java.io.Serializable;
import java.nio.ByteOrder;
import java.util.ArrayList;
import java.util.List;

public class Packet implements Serializable {
    public static final Logger LOG = LoggerFactory.getLogger(Packet.class);

    private long id;

    public long getId() {
        return id;
    }

    public void setId(long id) {
        this.id = id;
    }

    // 收到报文的时间戳，如果多线程处理需要对时间戳进行排序
    private long timestamp;

    public long getTimestamp() {
        return timestamp;
    }

    public enum PacketEnum {
        I, S, U
    }

    private PacketEnum packetType;

    private final byte[] controlFields = new byte[4];

    private final byte[] asduBytes;

    public Packet(byte[] apduBytes) {
        System.arraycopy(apduBytes, 0, controlFields, 0, 4);
        this.asduBytes = new byte[apduBytes.length - 4];
        System.arraycopy(apduBytes, 4, asduBytes, 0, apduBytes.length - 4);
        this.timestamp = System.currentTimeMillis();
    }

    public void analyse() {
        this.packetType = getPacketType();
        if (packetType == PacketEnum.I) {
            handleFrameI();
        }
        if (packetType == PacketEnum.S) {
            handleFrameS();
        }
        if (packetType == PacketEnum.U) {
            handleFrameU();
        }
    }

    private int sendNumber;
    private int receiveNumber;

    public enum TieIdentificationEnum {
        UNDEFINED(0, "未定义"),
        M_SP_NA_1(1, "单点遥信：无时标"),
        M_DP_NA_1(3, "双点遥信：无时标"),
        M_ST_NA_1(5, "步位置信息"),
        M_BO_NA_1(7, "32比特串"),
        M_ME_NA_1(9, "测量值，归一化值"),
        M_ME_NB_1(11, "测量值，标度化值"),
        M_ME_NC_1(13, "测量值，短浮点数"),
        M_IT_NA_1(15, "累计量"),
        M_PS_NA_1(20, "带状态检出的成组单点信息"),
        M_ME_ND_1(21, "不带品质描述的归一化量测值"),
        M_SP_TB_1(30, "带 CP56Time2a 时标的单点信息"),
        M_DP_TB_1(31, "带 CP56Time2a 时标的双点信息"),
        M_ST_TB_1(32, "带 CP56Time2a 时标的步位置信息"),
        M_BO_TB_1(33, "带时标CP56Time2a的32比特串"),
        M_ME_TD_1(34, "带时标CP56Time2a的归一化测量值"),
        M_ME_TE_1(35, "带时标CP56Time2a的标量化测量值"),
        M_ME_TF_1(36, "带时标CP56Time2a的浮点型测量值"),
        M_IT_TB_1(37, "带时标CP56Time2a的累计值"),
        M_EP_TD_1(38, "带时标CP56Time2a的继电器保护装置事件"),
        M_EP_TE_1(39, "带时标CP56Time2a的继电器保护装置成组启动事件"),
        M_EP_TF_1(40, "带时标CP56Time2a的继电器保护装置成组输出电路信息"),
        M_FT_NA_1(42, "故障事件信息"),
        M_IT_NB_1(206, "累计量，短浮点数"),
        M_IT_TC_1(207, "带 CP56Time2a 时标的累计量，短浮点数"),
        C_SC_NA_1(45, "单点遥控"), // C(Command控制)_SC(Single Command单命令)_NA(No Additional Information没有附加信息体)_1(版本号)
        C_SC_NB_1(46, "双点遥控"), // C(Command控制)_SC(Single Command单命令)_NB(With Additional Information带有附加信息体)_1(版本号)
        C_RC_NA_1(47, "步调节命令"),
        C_SE_NA_1(48, "设点：归一化值"),
        C_SE_NB_1(49, "设点：标度化值"),
        C_SE_NC_1(50, "设点：浮点数"),
        C_BO_NA_1(51, "32比特串"),
        C_SC_TA_1(58, "带 CP56Time2a 时标的单点遥控"),
        C_DC_TA_1(59, "带时标CP56Time2a的双命令"),
        C_RC_TA_1(60, "带时标CP56Time2a的步调节命令"),
        C_SE_TA_1(61, "带时标CP56Time2a的设点命令，归一化值"),
        C_SE_TB_1(62, "带时标CP56Time2a的设点命令，标量值"),
        C_SE_TC_1(63, "带时标CP56Time2a的设点命令，短浮点值"),
        C_BO_TA_1(64, "带时标CP56Time2a的32比特串"),
        M_EI_NA_1(70, "初始化结束"),
        C_IC_NA_1(100, "站总召唤命令"),
        C_CI_NA_1(101, "电能量召唤命令"),
        C_RD_NA_1(102, "读命令"),
        C_CS_NA_1(103, "时钟同步命令"),
        C_TS_NA_1(104, "测试命令"),
        C_RP_NA_1(105, "复位进程命令"),
        C_TS_TA_1(107, "带时标CP56Time2a的测试命令"),
        P_ME_NA_1(110, "归一化测量值"),
        P_ME_NB_1(111, "标度化测量值"),
        P_ME_NC_1(112, "短浮点数测量值"),
        P_ME_ND_1(113, "参数激活"),
        C_SR_NA_1(200, "切换定值区"),
        C_RR_NA_1(201, "读定值区号"),
        C_RS_NA_1(202, "读参数和定值"),
        C_WS_NA_1(203, "写参数和定值"),
        F_FR_NA_1(210, "文件传输"),
        F_SR_NA_1(211, "软件升级");
        // 136 扩展多点设值

        private final int value;
        private final String description;

        TieIdentificationEnum(int value, String description) {
            this.value = value;
            this.description = description;
        }

        public int getValue() {
            return value;
        }

        public String getDescription() {
            return description;
        }

        public static TieIdentificationEnum fromValue(int value) {
            for (TieIdentificationEnum type : TieIdentificationEnum.values()) {
                if (type.getValue() == value) {
                    return type;
                }
            }
            return UNDEFINED;
        }
    }

    private TieIdentificationEnum tieIdentification;

    public enum UnnumberedControlFunctionEnum {
        UNDEFINED(0, "未定义"),
        TEST_FR_CMD(1, "测试帧（命令）"),
        TEST_FR_ACK(2, "测试帧（确认）"),
        STOP_DT_CMD(3, "停止帧（命令）"),
        STOP_DT_ACK(4, "停止帧（确认）"),
        START_DT_CMD(5, "启动帧（命令）"),
        START_DT_ACK(6, "启动帧（确认）");
        private final int value;
        private final String description;

        UnnumberedControlFunctionEnum(int value, String description) {
            this.value = value;
            this.description = description;
        }

        public int getValue() {
            return value;
        }
    }

    private UnnumberedControlFunctionEnum unnumberedControlFunction;

    public static class VSQ implements Serializable {
        public VSQ(byte vqs) {
            this.num = vqs & 0x7F;
            this.sq = vqs >>> 7 & 0x1;
        }

        private final int sq;
        private final int num;

        public int getSq() {
            return sq;
        }

        public int getNum() {
            return num;
        }
    }

    private VSQ vsq;

    public enum CauseEnum {
        UNUSED(0, "未用"),
        PERIODIC_OR_CYCLIC(1, "周期、循环 per/cyc"),
        BACKGROUND_SCAN(2, "背景扫描 back"),
        SPONTANEOUS(3, "突发(自发) spont"),
        INITIALIZATION(4, "初始化 init"),
        REQUEST_OR_REQUESTED(5, "请求或者被请求 req"),
        ACTIVATION(6, "激活 act"),
        ACTIVATION_CONFIRMATION(7, "激活确认 actcon"),
        DEACTIVATION(8, "停止激活 deact"),
        DEACTIVATION_CONFIRMATION(9, "停止激活确认 deactcon"),
        ACTIVATION_TERMINATION(10, "激活终止 actterm"),
        FILE_TRANSFER(13, "文件传输 file"),
        RESPONSE_TO_GENERAL_INTERROGATION(20, "响应站召唤 introgen"),
        RESPONSE_TO_GROUP_1(21, "响应第1组召唤"),
        RESPONSE_TO_GROUP_2(22, "响应第2组召唤"),
        RESPONSE_TO_GROUP_3(23, "响应第3组召唤"),
        RESPONSE_TO_GROUP_4(24, "响应第4组召唤"),
        RESPONSE_TO_GROUP_5(25, "响应第5组召唤"),
        RESPONSE_TO_GROUP_6(26, "响应第6组召唤"),
        RESPONSE_TO_GROUP_7(27, "响应第7组召唤"),
        RESPONSE_TO_GROUP_8(28, "响应第8组召唤"),
        RESPONSE_TO_GROUP_9(29, "响应第9组召唤"),
        RESPONSE_TO_GROUP_10(30, "响应第10组召唤"),
        RESPONSE_TO_GROUP_11(31, "响应第11组召唤"),
        RESPONSE_TO_GROUP_12(32, "响应第12组召唤"),
        RESPONSE_TO_GROUP_13(33, "响应第13组召唤"),
        RESPONSE_TO_GROUP_14(34, "响应第14组召唤"),
        RESPONSE_TO_GROUP_15(35, "响应第15组召唤"),
        RESPONSE_TO_GROUP_16(36, "响应第16组召唤"),
        UNKNOWN_TYPE_IDENTIFIER(44, "未知的类型标识"),
        UNKNOWN_CAUSE_OF_TRANSMISSION(45, "未知的传送原因"),
        UNKNOWN_ASDU_COMMON_ADDRESS(46, "未知的应用服务数据单元公共地址"),
        UNKNOWN_INFORMATION_OBJECT_ADDRESS(47, "未知的信息对象地址"),
        REMOTE_CONTROL_EXECUTION_SOFT_PRESS_ERROR(48, "遥控执行软压板状态错误"),
        REMOTE_CONTROL_EXECUTION_TIMESTAMP_ERROR(49, "遥控执行时间戳错误"),
        REMOTE_CONTROL_EXECUTION_SIGNATURE_AUTH_ERROR(50, "遥控执行数字签名认证错误");

        private final int code;
        private final String description;

        CauseEnum(int code, String description) {
            this.code = code;
            this.description = description;
        }

        public int getCode() {
            return code;
        }

        public String getDescription() {
            return description;
        }

        @Override
        public String toString() {
            return String.format("Cause[code=%d, description=%s]", code, description);
        }

        /**
         * 根据code获取对应的Cause枚举值
         *
         * @param code 传送原因代码
         * @return 对应的Cause枚举值，如果未找到则返回null
         */
        public static CauseEnum fromCode(int code) {
            for (CauseEnum cause : values()) {
                if (cause.getCode() == code) {
                    return cause;
                }
            }
            return null;
        }
    }

    public static class COT implements Serializable {
        public COT(byte[] cot) {
            this.causeIndex = cot[0] & 0x3F;
            this.pn = cot[0] >>> 6 & 0x1;
            this.t = cot[0] >>> 7 & 0x1;
            this.srcAdr = cot[1]; // 源发地址
            this.cause = CauseEnum.fromCode(this.causeIndex);
        }

        private final int causeIndex;
        private final int pn;
        private final int t;
        private final byte srcAdr;
        private final CauseEnum cause;

        public CauseEnum getCause() {
            return cause;
        }
    }

    private COT cot;
    // ASDU地址
    private byte[] asduAddress;

    private int asduAddressInt;

    /**
     * 遥信或遥控对象
     */
    public static class DigitalSignals implements Serializable {
        public DigitalSignals(int infoAdr, byte qd, int infoBitNum, CP56Time2a cp56Time2a) {
            this.infoAdr = infoAdr;
            this.iv = qd >>> 7 & 0x1;
            this.nt = qd >>> 6 & 0x1;
            this.sb = qd >>> 5 & 0x1;
            this.bl = qd >>> 4 & 0x1;
            if (infoBitNum == 1) {
                this.spi = qd & 0x1;
            } else if (infoBitNum == 2) {
                this.spi = qd & 0x3;
            } else {
                throw new RuntimeException("infoBitNum 只能设置单/双点位信息");
            }
            this.cp56Time2a = cp56Time2a;
        }

        private final int infoAdr;
        /**
         * 有效标志位
         */
        private final int iv;
        /**
         * 刷新标志位
         */
        private final int nt;
        /**
         * 取代标志位
         */
        private final int sb;
        /**
         * 封锁标志位
         */
        private final int bl;
        /**
         * 遥信状态值。单点遥信，0分1合；双点遥信，1开2合，0和3为中间状态。
         */
        private final int spi;
        private final CP56Time2a cp56Time2a;

        public int getInfoAdr() {
            return infoAdr;
        }

        public int getSpi() {
            return spi;
        }

        public CP56Time2a getCp56Time2a() {
            return cp56Time2a;
        }

        public int getIv() {
            return iv;
        }

        public int getNt() {
            return nt;
        }

        public int getSb() {
            return sb;
        }

        public int getBl() {
            return bl;
        }
    }

    private List<DigitalSignals> digitalSignals = new ArrayList<>();

    /**
     * 遥测或遥调对象
     */
    public static class AnalogSignals implements Serializable {
        public AnalogSignals(int infoAdr, double val, CP56Time2a cp56Time2a, int qds) {
            this.infoAdr = infoAdr;
            this.val = val;
            if (!Double.isFinite(val)) {
                this.val = 0.0;
            }
            this.cp56Time2a = cp56Time2a;
            this.qds = qds; // 品质描述词
        }

        private final int infoAdr;
        private double val;
        private final CP56Time2a cp56Time2a;
        private final int qds;

        public int getInfoAdr() {
            return infoAdr;
        }

        public double getVal() {
            return val;
        }

        public int getQds() {
            return qds;
        }
    }

    private List<AnalogSignals> analogSignals = new ArrayList<>();

    public static class CP56Time2a implements Serializable {
        public CP56Time2a(byte[] time) {
            this.millisecond = ((time[1] & 0xFF) << 8) | (time[0] & 0xFF);
            this.minute = time[2] & 0x3F;
            this.hour = time[3] & 0x1F;
            this.dayOfMonth = time[4] & 0x1F;
            this.dayOfWeek = time[4] >>> 5 & 0x07;
            this.month = time[5] & 0x0F;
            this.year = time[6] & 0x7F;
        }

        private final int millisecond;
        private final int minute;
        private final int hour;
        private final int dayOfMonth;
        private final int dayOfWeek;
        private final int month;
        private final int year;

        @Override
        public String toString() {
            return 2000 + year + "-" + month + "-" + dayOfMonth + " " + hour + ":" + minute + ":" + millisecond;
        }
    }

    public byte getControlField(int index) {
        return controlFields[index];
    }

    public byte[] getAsduBytes() {
        return asduBytes;
    }

    public PacketEnum getPacketType() {
        byte firstControlByte = getControlField(0);
        int frameType0 = getBit(firstControlByte, 0);
        int frameType1 = getBit(firstControlByte, 1);

        if (frameType0 == 0) {
            return PacketEnum.I;
        }
        if (frameType1 == 0) {
            return PacketEnum.S;
        }
        return PacketEnum.U;
    }

    public TieIdentificationEnum getTieIdentification() {
        return tieIdentification;
    }

    public void setTieIdentification(TieIdentificationEnum tieIdentification) {
        this.tieIdentification = tieIdentification;
    }

    public VSQ getVsq() {
        return vsq;
    }

    public void setVsq(VSQ vsq) {
        this.vsq = vsq;
    }

    public COT getCot() {
        return cot;
    }

    public void setCot(COT cot) {
        this.cot = cot;
    }

    public byte[] getAsduAddress() {
        return asduAddress;
    }

    public void setAsduAddress(byte[] asduAddress) {
        this.asduAddress = asduAddress;
    }

    public int getAsduAddressInt() {
        return asduAddressInt;
    }

    public void addDigitalSignals(DigitalSignals digitalSignals) {
        this.digitalSignals.add(digitalSignals);
    }

    public void addAnalogSignals(AnalogSignals analogSignals) {
        this.analogSignals.add(analogSignals);
    }

    public List<DigitalSignals> getDigitalSignals() {
        return digitalSignals;
    }

    public List<AnalogSignals> getAnalogSignals() {
        return analogSignals;
    }

    public static int readUnsignedShort(byte[] bytes, ByteOrder byteOrder) {
        if (byteOrder == ByteOrder.BIG_ENDIAN) {
            return (bytes[0] & 0xFF) << 8 | (bytes[1] & 0xFF);
        } else {
            return (bytes[1] & 0xFF) << 8 | (bytes[0] & 0xFF);
        }
    }

    public static short readUnsignedByte(byte b) {
        return (short) (b & 0xFF);
    }

    public static int getBit(byte b, int index) {
        return (b >>> index) & 1;
    }

    private void handleFrameI() {
        byte[] bytes = new byte[2];
        bytes[0] = getControlField(0);
        bytes[1] = getControlField(1);
        this.sendNumber = Packet.readUnsignedShort(bytes, ByteOrder.LITTLE_ENDIAN) >>> 1;
        bytes[0] = getControlField(2);
        bytes[1] = getControlField(3);
        this.receiveNumber = Packet.readUnsignedShort(bytes, ByteOrder.LITTLE_ENDIAN) >>> 1;
        ByteBuf asduBytesBuf = Unpooled.buffer(this.asduBytes.length);
        try {
            asduBytesBuf.writeBytes(this.asduBytes);
            this.tieIdentification = TieIdentificationEnum.fromValue(asduBytesBuf.readUnsignedByte());
            if (tieIdentification == TieIdentificationEnum.UNDEFINED) {
                return;
            }
            this.vsq = new VSQ(asduBytesBuf.readByte());
            byte[] cotBytes = new byte[2];
            asduBytesBuf.readBytes(cotBytes);
            this.cot = new COT(cotBytes);
            this.asduAddress = new byte[2];
            asduBytesBuf.readBytes(asduAddress);
            if (asduBytes.length > 0) {
                asduAddressInt = ((asduAddress[0] & 0xFF) | (asduAddress[1] & 0xFF) << 8);
            }

            if (asduBytesBuf.isReadable()) {
                byte[] infoObjectBytes = new byte[asduBytesBuf.readableBytes()];
                asduBytesBuf.readBytes(infoObjectBytes);
                InfoObjectParser parser = ASDUParserFactory.GetParser(tieIdentification);
                if (parser != null) {
                    parser.parse(this, infoObjectBytes);
                }
            }
        } finally {
            asduBytesBuf.release();
        }
    }

    private void handleFrameS() {
        byte[] bytes = new byte[2];
        bytes[0] = getControlField(2);
        bytes[1] = getControlField(3);
        this.receiveNumber = Packet.readUnsignedShort(bytes, ByteOrder.LITTLE_ENDIAN) >>> 1;
    }

    private void handleFrameU() {
        byte field = getControlField(0);
        if (getBit(field, 3) == 1) {
            unnumberedControlFunction = UnnumberedControlFunctionEnum.START_DT_CMD;
        }
        if (getBit(field, 4) == 1) {
            unnumberedControlFunction = UnnumberedControlFunctionEnum.START_DT_ACK;
        }
        if (getBit(field, 5) == 1) {
            unnumberedControlFunction = UnnumberedControlFunctionEnum.STOP_DT_CMD;
        }
        if (getBit(field, 6) == 1) {
            unnumberedControlFunction = UnnumberedControlFunctionEnum.STOP_DT_ACK;
        }
        if (getBit(field, 7) == 1) {
            unnumberedControlFunction = UnnumberedControlFunctionEnum.TEST_FR_CMD;
        }
        if (getBit(field, 8) == 1) {
            unnumberedControlFunction = UnnumberedControlFunctionEnum.TEST_FR_ACK;
        }
    }

}
